---
title: "Basics"
slug: /getting-started/quickstarts/basics
description: "Learn about Dagger's core concepts, including containers, functions, and modules"
---

import PartialIde from '@partials/_ide.mdx';
import VideoPlayer from '@components/VideoPlayer';

Welcome to Dagger, a general-purpose composition engine for containerized workflows.

Dagger is a modular, composable platform designed to replace complex systems glued together with artisanal scripts - for example, complex integration testing environments, data processing pipelines, and AI agent workflows. It is open source and works with any compute platform or technology stack, automatically optimizing for speed and cost.

## Requirements

This quickstart will take you approximately 10 minutes to complete. You should be familiar with programming in Go, Python, TypeScript, PHP, or Java.

Before beginning, ensure that:
- you have [installed the Dagger CLI](../../installation.mdx).
- you have a container runtime installed on your system and running. This can be [Docker](https://docs.docker.com/engine/install/), [Podman](../../../reference/container-runtimes/podman.mdx), [nerdctl](../../../reference/container-runtimes/nerdctl.mdx), [Apple Container](../../../reference/container-runtimes/apple-container.mdx), or other Docker-like systems.
- you have a GitHub account (optional, only if configuring Dagger Cloud).

## Create containers

Dagger works by expressing workflows as combinations of functions from the [Dagger API](../../../extending/index.mdx), which exposes a range of tools for working with containers, files, directories, network services, secrets. You call the Dagger API from the shell ([Dagger Shell](../../../introduction/features/shell.mdx)) or from code ([custom Dagger Functions](../../../extending/modules/functions.mdx) written in a programming language).

[Dagger Shell](../../../introduction/features/shell.mdx) is an interactive client for the Dagger API, giving you typed objects, built-in documentation, and access to a cross-language ecosystem of reusable modules.

You launch it by typing

```shell
dagger
```

Here's an example of using it to build and return an Alpine container:

```shell
container | from alpine
```

You can open an interactive terminal session with the running container:

```shell
container | from alpine | terminal
```

This drops you into an interactive terminal running the `bash` shell. You can use this to interact with the running container, as shown below:

<VideoPlayer src="/img/current_docs/getting-started/quickstarts/basics/terminal-1.webm" alt="Terminal demonstration" />

## Execute commands

You can also run commands in the container and return the output. Here's an example of returning the output of the `uname` command:

```shell
container | from alpine | with-exec uname | stdout
```

Here's an example of installing the `curl` package in the container and using it to retrieve a webpage:

```shell
container | from alpine | with-exec apk add curl | with-exec curl https://dagger.io | stdout
```

:::tip GETTING HELP
The Dagger API is extensively documented but if you're unsure how to proceed at any point, simply append `.help` to your in-progress workflow for context-sensitive assistance on what you can do next. For example:

```shell
container | from alpine | .help
container | from alpine | .help with-directory
container | from alpine | .help with-exec
```
:::

## Add files and directories

Once you've got a container, you continue using the Dagger API to modify it, by adding files or directories to it. You can use directories from the Dagger host's filesystem or remote Git repositories. Here's an example of adding Dagger's own open source GitHub repository to the container:

```shell
container | from alpine | with-directory /src https://github.com/dagger/dagger
```

Here's another example, this time creating a new file in the container:

```shell
container | from alpine | with-new-file /hi.txt "Hello from Dagger!"
```

Did it work? To look inside, request another interactive terminal session with the running container:

```shell
container | from alpine | with-new-file /hi.txt "Hello from Dagger!" | terminal
```

Check if the file exists, as shown below:

<VideoPlayer src="/img/current_docs/getting-started/quickstarts/basics/terminal-2.webm" alt="Terminal file check demonstration" />

:::tip INTERACTIVE CONTAINER DEBUGGING
The `terminal` function is very useful for [debugging and experimenting](../../../introduction/features/observability.mdx), since it allows you to interact directly with containers and inspect their state, at any stage of your Dagger Function execution.
:::

## Chain functions in the shell

What you just did (and have been doing since the start) is chaining one Dagger API function call to another with the pipe (`|`) operator. This is one of Dagger's most powerful features, as it allows you create dynamic workflows in a single command - no context switching between Dockerfile creation, build commands, and registry pushes.

Dagger's documentation has [numerous examples of function chaining](../../../extending/modules/chaining.mdx) in action but here's one more: creating an Alpine container, dropping in a text file with a custom message, setting it to display that message when run, and publishing it to a temporary registry - all in a single command!

```shell
container | from alpine | with-new-file /hi.txt "Hello from Dagger!" |
  with-entrypoint cat /hi.txt | publish ttl.sh/hello
```

<VideoPlayer src="/img/current_docs/getting-started/quickstarts/basics/publish-shell.webm" alt="Publishing demonstration" />

## Write custom functions

As your workflows become more complex, you'll start wishing you could make them more reusable, repeatable and shareable. To do this, encapsulate your workflows into [custom Dagger Functions](../../../extending/modules/functions.mdx). These are just regular code consisting of a series of method/function calls, such as "pull a container image", "copy a file", "forward a TCP port", and so on, which can be chained together. They are written in a programming language using a type-safe Dagger SDK and packaged into [modules](../../../introduction/features/reusability.mdx).

:::tip IDE INTEGRATION
<PartialIde />
:::

Here's the previous example, rewritten as a Dagger Function:

<Tabs groupId="language" queryString="sdk">
<TabItem value="go" label="Go">
```go
func (m *Basics) Publish(ctx context.Context) (string, error) {
	return dag.Container().
		From("alpine:latest").
		WithNewFile("/hi.txt", "Hello from Dagger!").
		WithEntrypoint([]string{"cat", "/hi.txt"}).
		Publish(ctx, "ttl.sh/hello")
}
```

To use this function, initialize a new Dagger module using the command below, and then update the auto-generated `.dagger/main.go` file to include the function code above:

```shell
dagger init --sdk=go --name=basics
```
</TabItem>
<TabItem value="python" label="Python">
```python
@function
async def publish(self) -> str:
    return await (
        dag.container()
        .from_("alpine:latest")
        .with_new_file("/hi.txt", "Hello from Dagger!")
        .with_entrypoint(["cat", "/hi.txt"])
        .publish("ttl.sh/hello")
    )
```

To use this function, initialize a new Dagger module using the command below, and then update the auto-generated `.dagger/src/basics/main.py` file to include the function code above:

```shell
dagger init --sdk=python --name=basics
```
</TabItem>
<TabItem value="typescript" label="TypeScript">
```typescript
@func()
async publish(): Promise<string> {
  return dag
    .container()
    .from("alpine:latest")
    .withNewFile("/hi.txt", "Hello from Dagger!")
    .withEntrypoint(["cat", "/hi.txt"])
    .publish("ttl.sh/hello")
}
```
To use this function, initialize a new Dagger module using the command below, and then update the auto-generated `.dagger/src/index.ts` file to include the function code above:

```shell
dagger init --sdk=typescript --name=basics
```
</TabItem>
<TabItem value="php" label="PHP">
```php
#[DaggerFunction]
public function publish(): string
{
    return dag()
        ->container()
        ->from('alpine:latest')
        ->withNewFile('/hi.txt', 'Hello from Dagger!')
        ->withEntrypoint(['cat', '/hi.txt'])
        ->publish('ttl.sh/hello');
}
```

To use this function, initialize a new Dagger module using the command below, and then update the auto-generated `.dagger/src/Basics.php` file to include the function code above:

```shell
dagger init --sdk=php --name=basics
```
</TabItem>
<TabItem value="java" label="Java">
```java
@Function
public String publish()
    throws InterruptedException, ExecutionException, DaggerQueryException {
  return dag()
      .container()
      .from("alpine:latest")
      .withNewFile("/hi.txt", "Hello from Dagger!")
      .withEntrypoint(List.of("cat", "/hi.txt"))
      .publish("ttl.sh/hello");
}
```
To use this function, initialize a new Dagger module using the command below, and then update the auto-generated `.dagger/src/main/java/io/dagger/modules/basics/Basics.java` file to include the function code above:

```shell
dagger init --sdk=java --name=basics
```
</TabItem>
</Tabs>

When you create a custom Dagger Function and tell Dagger about it (by installing the corresponding module), the Dagger API is [dynamically extended](../../../reference/api/internals.mdx#dynamic-api-extension) to include that new function. You then call this function from Dagger Shell, or from the command-line using `dagger call`, in exactly the same way as you would call functions from the original Dagger API.

<VideoPlayer src="/img/current_docs/getting-started/quickstarts/basics/publish-code.webm" alt="Publishing demonstration" />

## Chain functions in code

Function chaining works the same way, whether you're writing Dagger Function code using a Dagger SDK or using Dagger Shell. The following are equivalent:

<Tabs groupId="language" queryString="sdk">
<TabItem value="go" label="Go">

```go
// Returns a base container
func (m *Basics) Base() *dagger.Container {
	return dag.Container().From("cgr.dev/chainguard/wolfi-base")
}

// Builds on top of base container and returns a new container
func (m *Basics) Build() *dagger.Container {
	return m.Base().WithExec([]string{"apk", "add", "bash", "git"})
}

// Builds and publishes a container
func (m *Basics) BuildAndPublish(ctx context.Context) (string, error) {
	return m.Build().Publish(ctx, "ttl.sh/bar")
}
```

</TabItem>
<TabItem value="python" label="Python">

```python
@object_type
class Basics:
    @function
    def base(self) -> dagger.Container:
        """Returns a base container"""
        return dag.container().from_("cgr.dev/chainguard/wolfi-base")

    @function
    def build(self) -> dagger.Container:
        """Builds on top of base container and returns a new container"""
        return self.base().with_exec(["apk", "add", "bash", "git"])

    @function
    async def build_and_publish(self) -> str:
        """Builds and publishes a container"""
        return await self.build().publish("ttl.sh/bar")
```

</TabItem>
<TabItem value="typescript" label="TypeScript">

```typescript
@object()
class Basics {
  /**
   * Returns a base container
   */
  @func()
  base(): Container {
    return dag.container().from("cgr.dev/chainguard/wolfi-base")
  }

  /**
   * Builds on top of base container and returns a new container
   */
  @func()
  build(): Container {
    return this.base().withExec(["apk", "add", "bash", "git"])
  }

  /**
   * Builds and publishes a container
   */
  @func()
  async buildAndPublish(): Promise<string> {
    return await this.build().publish("ttl.sh/bar")
  }
}
```

</TabItem>
<TabItem value="php" label="PHP">

```php
#[DaggerObject]
class Basics
{
    #[DaggerFunction]
    #[Doc('Returns a base container')]
    public function base(): Container
    {
        return dag()
            ->container()
            ->from('cgr.dev/chainguard/wolfi-base');
    }

    #[DaggerFunction]
    #[Doc('Builds on top of base container and returns a new container')]
    public function build(): Container
    {
        return $this
            ->base()
            ->withExec(['apk', 'add', 'bash', 'git']);
    }

    #[DaggerFunction]
    #[Doc('Builds and publishes a container')]
    public function buildAndPublish(): string
    {
        return $this
            ->build()
            ->publish('ttl.sh/bar');
    }
}
```

</TabItem>
<TabItem value="java" label="Java">

```java
@Object
public class Basics {
  /**
   * Returns a base container
   */
  @Function
  public Container base() {
    return dag().container().from("cgr.dev/chainguard/wolfi-base");
  }

  /**
   * Builds on top of base container and returns a new container
   */
  @Function
  public Container build() {
    return this.base().withExec(List.of("apk", "add", "bash", "git"));
  }

  /**
   * Builds and publishes a container
   */
  @Function
  public String buildAndPublish()
      throws InterruptedException, ExecutionException, DaggerQueryException {
    return this.build().publish("ttl.sh/bar");
  }
}
```

</TabItem>
<TabItem value="System shell">
```shell
# all equivalent
dagger -c 'base | with-exec apk add bash git | publish ttl.sh/bar'
dagger -c 'build | publish ttl.sh/bar'
dagger -c build-and-publish
```
</TabItem>
<TabItem value="Dagger Shell">
```shell title="First type 'dagger' for interactive mode."
# all equivalent
base | with-exec apk add bash git | publish ttl.sh/bar
build | publish ttl.sh/bar
build-and-publish
```
</TabItem>
</Tabs>

:::tip FUNCTION NAMES
When calling Dagger Functions, all names (functions, arguments, fields, etc.) are converted into a shell-friendly "kebab-case" style. This is why a Dagger Function named `FooBar` in Go, `foo_bar` in Python and `fooBar` in TypeScript/PHP/Java is called as `foo-bar` in Dagger Shell or on the command-line.
:::

## Use arguments and return values

Dagger Functions are just like regular functions: they accept [arguments](../../../extending/modules/arguments.mdx) and [return values](../../../extending/modules/return-types.mdx). In addition to common types (string, boolean, integer, arrays...), the Dagger API also defines powerful [types](../../../getting-started/types/index.mdx) which Dagger Functions can use, such as `Directory`, `Container`, `Service`, `Secret`, and many more.

Here's a revision of the previous example which splits it into two smaller functions: one to build the container, and one to publish it. The builder function accepts the container image string as an argument and returns a `Container` type. The publisher function accepts a `Container` type as argument and returns the image identifier as a string.

<Tabs groupId="language" queryString="sdk">
<TabItem value="go" label="Go">
```go
func (m *Basics) Build(
	// +default "alpine:latest"
	image string,
) *dagger.Container {
	return dag.Container().
		From(image).
		WithNewFile("/hi.txt", "Hello from Dagger!")
}

func (m *Basics) Publish(
	ctx context.Context,
	// +default "alpine:latest"
	image string,
) (string, error) {
	return m.Build(image).
		WithEntrypoint([]string{"cat", "/hi.txt"}).
		Publish(ctx, "ttl.sh/hello")
}
```
</TabItem>
<TabItem value="python" label="Python">
```python
@function
def build(self, image: str = "alpine:latest") -> dagger.Container:
    return (
        dag.container().from_(image).with_new_file("/hi.txt", "Hello from Dagger!")
    )

@function
async def publish(self, image: str = "alpine:latest") -> str:
    return await (
        self.build(image)
        .with_entrypoint(["cat", "/hi.txt"])
        .publish("ttl.sh/hello")
    )
```
</TabItem>
<TabItem value="typescript" label="TypeScript">
```typescript
@func()
build(image = "alpine:latest"): Container {
  return dag
    .container()
    .from(image)
    .withNewFile("/hi.txt", "Hello from Dagger!")
}

@func()
async publish(image = "alpine:latest"): Promise<string> {
  return this
    .build(image)
    .withEntrypoint(["cat", "/hi.txt"])
    .publish("ttl.sh/hello")
}
```
</TabItem>
<TabItem value="php" label="PHP">
```php
#[DaggerFunction]
public function build(string $image = 'alpine:latest'): Container
{
    return dag()
        ->container()
        ->from($image)
        ->withNewFile('/hi.txt', 'Hello from Dagger!');
}

#[DaggerFunction]
public function publish2(string $image = 'alpine:latest'): string
{
    return $this
        ->build($image)
        ->withEntrypoint(['cat', '/hi.txt'])
        ->publish('ttl.sh/hello');
}
```
</TabItem>
<TabItem value="java" label="Java">
```java
@Function
public Container build(@Default("alpine:latest") String image) {
  return dag()
      .container()
      .from("alpine:latest")
      .withNewFile("/hi.txt", "Hello from Dagger!");
}

@Function
public String publish2(@Default("alpine:latest") String image)
    throws InterruptedException, ExecutionException, DaggerQueryException {
  return this
      .build(image)
      .withEntrypoint(List.of("cat", "/hi.txt"))
      .publish("ttl.sh/hello");
}
```
</TabItem>
</Tabs>

:::tip SANDBOXING
Dagger Functions are fully "sandboxed" and do not have direct access to the host system. Therefore, host resources such as directories, files, environment variables, network services and so on must be explicitly passed to Dagger Functions as arguments. This "sandboxing" of Dagger Functions improves security, ensures reproducibility, and assists caching.
:::

## Install other modules

You can group Dagger Functions into [modules](../../../introduction/features/reusability.mdx) and share them with others - your team, your company, or the broader Dagger community. And just as others can use your modules, you too can use modules created and shared by others, to speed up your development and take advantage of best practices. The [Daggerverse](https://daggerverse.dev) is a free service run by Dagger, which indexes all publicly available Dagger modules, and lets you easily search and consume them.

![Daggerverse](/img/current_docs/getting-started/quickstarts/basics/daggerverse.jpg)

Here's an example of installing and using two modules from the Daggerverse: a Wolfi container builder and a Trivy container scanner:

<VideoPlayer src="/img/current_docs/getting-started/quickstarts/basics/modules.webm" alt="Modules demonstration" />

Here's what it looks like in code:

<Tabs groupId="language" queryString="sdk">
<TabItem value="go" label="Go">
```go
func (m *Basics) Check(ctx context.Context) (string, error) {
	ctr := dag.Wolfi().Container()
	return dag.Trivy().
		ScanContainer(ctx, ctr);
}
```
</TabItem>
<TabItem value="python" label="Python">
```python
@function
def check(self) -> str:
    ctr = dag.wolfi().container()
    return dag.trivy().scan_container(ctr)
```
</TabItem>
<TabItem value="typescript" label="TypeScript">
```typescript
@func()
check(): string {
  let ctr = dag.wolfi().container()
  return dag.trivy().scanContainer(ctr);
}
```
</TabItem>
<TabItem value="php" label="PHP">
```php
#[DaggerFunction]
public function check(): string
{
  $ctr = dag()
      ->wolfi()
      ->container();
  return dag()
      ->trivy()
      ->scanContainer($ctr);
}
```
</TabItem>
<TabItem value="java" label="Java">
```java
@Function
public String check()
    throws InterruptedException, ExecutionException, DaggerQueryException {
  Container ctr = dag()
      .wolfi()
      .container();
  return dag()
      .trivy()
      .scanContainer(ctr);
}
```
</TabItem>
</Tabs>

:::tip CROSS-LANGUAGE COLLABORATION
Dagger Functions can call other Dagger Functions, across languages. For example, a Dagger Function written in Python can call a Dagger Function written in Go, which can call another one written in TypeScript, and so on. This means that you no longer need to care which language your workflow is written in; you can use the one that you're most comfortable with or that best suits your requirements.
:::

## Speed things up

One of Dagger's most powerful features is its ability to [cache data across workflow runs](../../../introduction/features/caching.mdx). Dagger caches two types of data:

- [Layers]: Build instructions and the results of some API calls.
- [Volumes]: Contents of Dagger filesystem volumes.

Taken together, these two types of caching significantly reduce execution times. Here's an example: a Dagger Function that creates a cache volume to store the packages installed by `apt`:

<Tabs groupId="language" queryString="sdk">
<TabItem value="go" label="Go">
```go
func (m *Basics) Env(ctx context.Context) *dagger.Container {
	aptCache := dag.CacheVolume("apt-cache")
	return dag.Container().
		From("debian:latest").
		WithMountedCache("/var/cache/apt/archives", aptCache).
		WithExec([]string{"apt-get", "update"}).
		WithExec([]string{"apt-get", "install", "--yes", "maven", "mariadb-server"})
}
```
</TabItem>
<TabItem value="python" label="Python">
```python
@function
def env(self) -> dagger.Container:
    apt_cache = dag.cache_volume("apt-cache")
    return (
        dag.container()
        .from_("debian:latest")
        .with_mounted_cache("/var/cache/apt/archives", apt_cache)
        .with_exec(["apt-get", "update"])
        .with_exec(["apt-get", "install", "--yes", "maven", "mariadb-server"])
    )
```
</TabItem>
<TabItem value="typescript" label="TypeScript">
```typescript
@func()
env(): Container {
  let aptCache = dag.cacheVolume("apt-cache")
  return dag.container()
    .from("debian:latest")
    .withMountedCache("/var/cache/apt/archives", aptCache)
    .withExec(["apt-get", "update"])
    .withExec(["apt-get", "install", "--yes", "maven", "mariadb-server"])
}
```
</TabItem>
<TabItem value="php" label="PHP">
```php
#[DaggerFunction]
public function env(): Container
{
  $aptCache = dag()->cacheVolume('apt-cache');
  return dag()
    ->container()
    ->from('debian:latest')
    ->withMountedCache('/var/cache/apt/archives', $aptCache)
    ->withExec(['apt-get', 'update'])
    ->withExec(['apt-get', 'install', '--yes', 'maven', 'mariadb-server']);
}
```
</TabItem>
<TabItem value="java" label="Java">
```java
  @Function
  public Container env() {
    CacheVolume aptCache = dag().cacheVolume("apt-cache");
    return dag()
        .container()
        .from("debian:latest")
        .withMountedCache("/var/cache/apt/archives", aptCache)
        .withExec(List.of("apt-get", "update"))
        .withExec(List.of("apt-get", "install", "--yes", "maven", "mariadb-server"));
  }
```
</TabItem>
</Tabs>

Notice that when you call this Dagger Function multiple times, the second and subsequent runs are drastically faster than the first, since Dagger automatically reuses cached instructions and files from the cache.

<VideoPlayer src="/img/current_docs/getting-started/quickstarts/basics/caching.webm" alt="Caching demonstration" />

## Trace everything

Building and running workflows is only part of the problem - you also need a way to inspect and monitor them. Dagger provides [two powerful real-time observability tools](../../../introduction/features/observability.mdx): the [Dagger terminal UI (TUI)](../../../introduction/features/observability.mdx#terminal-ui), which you've already seen above, and [Dagger Cloud](https://dagger.cloud), a browser-based interface focused on tracing and debugging Dagger workflows.

Once configured, every time you execute a Dagger workflow, its operational telemetry is automatically sent to Dagger Cloud as a Trace and the workflow output includes a link to visualize the workflow run on Dagger Cloud. Here's an example:

<VideoPlayer src="/img/current_docs/introduction/features/trace.webm" alt="Trace demonstration" />

:::tip DAGGER CLOUD
[Dagger Cloud](https://dagger.io/cloud) sign-up is optional, and free of charge for a single user.
:::

## Next steps

Now that you know the basics of Dagger, continue your journey with the resources below:

- [Build a CI workflow](../ci/index.mdx)
- [Build an AI Agent](../agent/index.mdx)
- [Learn about the Dagger API](../../../extending/index.mdx)
- [Learn about Dagger Shell](../../../introduction/features/shell.mdx)
- [Learn about Dagger Functions](../../../extending/modules/functions.mdx)
- [Visit the Daggerverse](https://daggerverse.dev/)
- [See more examples in the Cookbook](../../../cookbook/index.mdx)
